package org.ice4apps.talk.subscriberCallHandler;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.sip.AuthInfo;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipServletContextEvent;
import javax.servlet.sip.SipServletListener;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TelURL;
import javax.servlet.sip.URI;

import org.ice4apps.util.IceLog;
//import org.ice4apps.talk.TalkUtil;
import org.ice4apps.talk.callManager.UserSessionMessageRouter;
import org.ice4apps.talk.entity.UserSessionType;
import org.ice4apps.talk.entity.MessageType;
import org.ice4apps.talk.entity.User;
import org.ice4apps.talk.entity.UserSessionMessage;
import org.ice4apps.talk.session.security.IceTalkAuthentication;

/* This is the entry point for any call originated by the subscriber. 
 * This servlet will Authenticate the request and pass the request to the Session Manager
 * To get the handler to the session manager,  
 */

public class SubscriberCallServlet extends SipServlet implements SipServletListener {
    public void init(ServletConfig config) throws ServletException {
    	super.init(config);
    }    

	public void doInvite(SipServletRequest request) {
		String fromUri = request.getFrom().getURI().toString();
		String toUri = request.getTo().getURI().toString(); 
		
//		TalkUtil talkUtil = TalkUtil.instance();
//		String FromUserAlias = talkUtil.getUser(request.getFrom().getURI());

		// TODO : Connect to the cache and get the user fromt the fromUri
//		User fromUser = talkUtil.getUser(FromUserAlias);
		User fromUser = new User();
		
		SipApplicationSession sipApplicationSession = request.getApplicationSession(true);
		// TODO: Remove after testing
		fromUser.setUserName("3061974012");
		fromUser.setPassword("password");
		fromUser.setId("3061974012");
		

		if (null == fromUser)
		{
			int errorResponse = SipServletResponse.SC_FORBIDDEN;
			SipServletResponse errorResp = request.createResponse(errorResponse);
			IceLog.info(this.getClass(), "BIJU Invite Forbidden");
			try {
				errorResp.send();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return;
		}

/*
        // TOOD: get the registration from the DB / cache
		String fromRegistration = getFromContact();
		if(fromRegistration == null) {
			try {
				request.createResponse(404).send();
			} catch (IOException e) {
				log.error("Can't send 404 response");
			}
			return;
		}
*/
		// TODO: Need to think carefully here. Since this is a singleton class, 
		// we might have some delay here on high load
		IceLog.info(this.getClass(), "SubscriberCallServlet Servlet Initialized");
		SipServletResponse resp = IceTalkAuthentication.instance().authenticate(request);
		if (SipServletResponse.SC_OK != resp.getStatus())
		{
			try {
				resp.send();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return;
		}
		
		String sessionId = request.getSession().getId();
		
		IceLog.info(this.getClass(), "SubscriberCallServlet Servlet sessionId : " + sessionId);
		if (null == sessionId)
		{
			int errorResponse = SipServletResponse.SC_FORBIDDEN;
			resp = request.createResponse(errorResponse);
			try {
				resp.send();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return;
		}
		
		// Ringing
		resp = request.createResponse(SipServletResponse.SC_TRYING);
		try {
			resp.send();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		IceLog.info(this.getClass(), "SubscriberCallServlet Servlet Send Trying");

		UserSessionMessage msg = new UserSessionMessage (MessageType.SIP_INVITE,
				UserSessionType.ORIGINATING,
				sessionId,
				fromUser,
				request);
		
		UserSessionMessageRouter r = UserSessionMessageRouter.instantiate();
		r.postMessage(msg);
		
		IceLog.info(this.getClass(), "SubscriberCallServlet Servlet done with Invite");
		/*		
		// Letz now wait for the Call Manager to wake us up 
		synchronized (this) {
			try {
				wait ();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		SipServletResponse response = (SipServletResponse)msg.getResponse();
		String toUserAlias = getDialedSubscriber (fromUser, talkUtil.getUser(request.getTo().getURI()));
		User toUser = new User();

		String toUri = getDestination();
		if(toUri == null) {
			try {
				request.createResponse(404).send();
			} catch (IOException e) {
				log.error("Can't send 404 response");
			}
			return;
		}

 		
		String fromUserDiaplay = fromUser.getDisplayName();
		String toUser = toRegistration.getUser().getName();
		
		// Extract sdp
		Object sdpObj = null;
		try {
			sipServletResponse.send();
			sdpObj = request.getContent();
		} catch (IOException e) {}
		byte[] sdpBytes = (byte[]) sdpObj;
		String sdp = new String(sdpBytes);
		
		// Create call participants to join a call
		CallParticipant fromParticipant = CallParticipantManager.instance().getCallParticipant(
				fromUri);
		fromParticipant.setName(fromUser);
		fromParticipant.setRegistration(fromRegistration);
		fromParticipant.setUri(fromRegistration.getUri());
		fromParticipant.setInitiator(true);
		
		CallParticipant toParticipant = CallParticipantManager.instance().getCallParticipant(
				toUri);
		toParticipant.setName(toUser);
		toParticipant.setRegistration(toRegistration);
		toParticipant.setUri(toUri);
		toParticipant.setBinding(toBinding);
		
		//Store the request/session in the registration
		fromParticipant.setInitialRequest(request);
		
		// Store reusable info for later in this session
		sipSession.setAttribute("participant", fromParticipant);
		sipSession.setAttribute("firstSipMessage", request);
		
		Conference conf = null;
		
		
		if(toParticipant.getConference() == null) {
			// If the callee is in a call, just call all his registered phones and join them to a new
			// conference where you can talk to him
			conf = ConferenceManager.instance().getNewConference();
			fromParticipant.setConference(conf);
			toParticipant.setConference(conf);
			toParticipant.setCallState(CallState.CONNECTING);
			fromParticipant.setCallState(CallState.CONNECTING);
			WorkspaceStateManager.instance().getWorkspace(fromUser).setOutgoing(toParticipant);
			callAction.dialParticipant(toParticipant);
			log.info("Calling inactive user");
		} else {
			// If the callee is in a call already we must ask to join that call
			conf = toParticipant.getConference();
			fromParticipant.setConference(conf);
			fromParticipant.setCallState(CallState.ASKING);
			log.info("Calling an active user, we must ask for permission");
		}
		WorkspaceStateManager.instance().getWorkspace(toUser).setIncoming(fromParticipant);
		
		
		mediaController.createConnection(PR_JNDI_NAME).modify("$", sdp);
		Events.instance().raiseEvent("incomingCall", request);
*/		
	}
	public void doResponse(SipServletResponse response)
			throws ServletException, IOException {
		IceLog.info(this.getClass(), "doResponse ");

		if(response.getMethod().equalsIgnoreCase("INVITE")) {
			int status = response.getStatus();
		}
			
			/*	
			if(status == 180) {
				participant.setCallState(CallState.RINGING);
			} else if (status == 200) {
				Object sdpObj = response.getContent();
				byte[] sdpBytes = (byte[]) sdpObj;
				String sdp = new String(sdpBytes);
				
				sipSession.setAttribute("firstSipMessage", response);
				mediaController.createConnection(PR_JNDI_NAME).modify("$", sdp);
				
			} else if(status == 401 || status == 407) {
				Integer authAttempts = (Integer) sipSession.getAttribute("authAttempts");
				if(authAttempts == null) authAttempts = 0;
				
				sipSession.setAttribute("authAttempts", authAttempts++);
				
				// If it doesn't succeed 3 times give up on authentication
				if(authAttempts > 3) return;
				
				// So we dialed a PSTN phone. Use auth info from the settings
				PstnGatewayAccount account = participant.getPstnGatewayAccount();
				if(account != null) {
					AuthInfo authInfo = sipFactory.createAuthInfo();
					authInfo.addAuthInfo(response.getStatus(), 
                            response.getChallengeRealms().next(), 
                            account.getUsername(), 
                            account.getPassword());
					SipServletRequest challengeRequest = response.getSession().createRequest(
							response.getRequest().getMethod());

					challengeRequest.addAuthHeader(response, authInfo);
					challengeRequest.send();

				}
			} else if(status >= 300){
				quitConference(participant, participant.getConference());
			}
		}
*/		
	}
/*	
	public void doBye(SipServletRequest request) throws ServletException,
			IOException {
		log.info("Got BYE request:\n" + request);
		
		SipServletResponse sipServletResponse = request.createResponse(SipServletResponse.SC_OK);
		sipServletResponse.send();		
		
		// Clean up the mess in the models
		CallParticipant participant = (CallParticipant) 
			sipSession.getAttribute("participant");
				
		Conference conference = participant.getConference();
		quitConference(participant, conference);		
	}

	private void quitConference(CallParticipant participant, Conference conf) throws IOException {

		participant.setCallState(CallState.DISCONNECTED);
		participant.setConference(null);

		// Remove the call from the callee GUI
		WorkspaceStateManager.instance().getWorkspace(participant.getName()).removeCall(participant);

		CallParticipant[] callParticipants = conf.getParticipants();
		log.info("number of participants left in the conference = #0", callParticipants.length);
		if(callParticipants.length == 1) {
			CallParticipant cp = callParticipants[0];
			WorkspaceStateManager.instance().getWorkspace(cp.getName()).endCall(cp);
		}

		// Remove the call from other users' GUIs
		for(CallParticipant cp : callParticipants) {
			WorkspaceStateManager.instance().getWorkspace(cp.getName()).removeCall(participant);
			String disconnectedUsername = participant.getName();

			// Determine if there are more call legs to phones registered under that user. If there are other
			// call legs, don't remove that call from user's workspace
			boolean remove = true;
			for(CallParticipant cp2 : callParticipants) {
				if(cp2.getName().equals(disconnectedUsername)) {
					remove = false;
				}
			}
			if(remove) WorkspaceStateManager.instance().getWorkspace(disconnectedUsername).removeCall(cp);
		}

		// Release media stuff if possible
		if(participant.getMsLink() != null) 
			participant.getMsLink().release();
		if(participant.getMsConnection() != null) 
			participant.getMsConnection().release();
		participant.setMsConnection(null);
		participant.setMsLink(null);
	}

	@Observer("connectionOpen")
	public void connectionOpen(MsConnectionEvent event) {
		SipServletMessage sipMessage = (SipServletMessage) sipSession.getAttribute("firstSipMessage");
		if(sipMessage instanceof SipServletRequest) {
			SipServletRequest request = (SipServletRequest) sipMessage;
			connectionOpenRequest(event, request);
		} else {
			SipServletResponse response = (SipServletResponse) sipMessage;
			connectionOpenResponse(event, response);
		}
	}

	private void connectionOpenResponse(MsConnectionEvent event, SipServletResponse response) {
		CallParticipant participant = 
			(CallParticipant) sipSession.getAttribute("participant");
		MsConnection connection = event.getConnection();
		participant.setMsConnection(connection);

		String sdp = event.getConnection().getLocalDescriptor();
		SipServletRequest ack = response.createAck();

		try {
			ack.setContent(sdp, "application/sdp");
			ack.send();
		} catch (Exception e) {
			log.error(e);
		}

		if(participant.getConference() != null) {
			mediaController.createLink(MsLinkMode.FULL_DUPLEX)
			.join(participant.getConference().getEndpointName(),
					connection.getEndpoint().getLocalName());
			System.out.println("RRRRRRRRRRRRRRRRRRRRRR Join " + participant.toString() + " in " + participant.getConference().getEndpointName());
		} else {
			throw new IllegalStateException("Participant doesnt have conference assigned " 
					+ participant.toString());
		}

	}
	
	private void connectionOpenRequest(MsConnectionEvent event, SipServletRequest inviteRequest) {
		CallParticipant participant = 
			(CallParticipant) sipSession.getAttribute("participant");
		MsConnection connection = event.getConnection();
		participant.setMsConnection(connection);

		String sdp = event.getConnection().getLocalDescriptor();
		SipServletResponse sipServletResponse = inviteRequest
				.createResponse(SipServletResponse.SC_OK);

		try {
			sipServletResponse.setContent(sdp, "application/sdp");
			sipServletResponse.send();
		} catch (Exception e) {
			log.error(e);
		}
		
		if(participant.getConference() != null &&
				!CallState.ASKING.equals(participant.getCallState()) // Yeah, we must ask for permissing in this case
				) {
			mediaController.createLink(MsLinkMode.FULL_DUPLEX)
			.join(participant.getConference().getEndpointName(),
					connection.getEndpoint().getLocalName());
		}
	}
	
	
	@Observer("linkConnected")
	public void doLinkConnected(MsLinkEvent event) {
		synchronized (PR_JNDI_NAME) {

			MsEndpoint endpoint = event.getSource().getEndpoints()[0];

			CallParticipant participant = 
				(CallParticipant) sipSession.getAttribute("participant");

			log.info("Link connected for " + participant);

			mediaSessionStore.setMsEndpoint(endpoint);
			ivrHelper.detectDtmf();

			participant.setCallState(CallState.CONNECTED);

			Conference conf = participant.getConference();
			
			// We should upgrade the call state to "active" for all participants (most already have it)
			CallParticipant[] ps = conf.getParticipants();
			for(CallParticipant p : ps) {
				if(p!=participant) {

					if(p.getCallState().equals(CallState.CONNECTED)) {
						p.setCallState(CallState.INCALL);
						try {
							endRingback(p);
						} catch (Exception e) {}
						participant.setCallState(CallState.INCALL);

						// Many of these are not needed, but it's hard to debug what is missing in corner cases
						WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(participant);
						WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(p);
						WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(participant);
						WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(p);
					}

				}
				if(p.getCallState().equals(CallState.INCALL)) {
					participant.setCallState(CallState.INCALL);
					// Many of these are not needed, but it's hard to debug what is missing in corner cases
					WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(p);
					WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(participant);
					WorkspaceStateManager.instance().getWorkspace(participant.getName()).setOngoing(participant);
					WorkspaceStateManager.instance().getWorkspace(p.getName()).setOngoing(p);
				}	
			}

			// And if this is the first participant in the conference, let's determine which endpoint we use
			if(participant != null) {
				participant.setMsLink(event.getSource());
				if(conf.getEndpoint() == null) {
					conf.setEndpoint(endpoint);
					System.out.println("RRRRRRRRRRRRRRRRRRRR conf endpoint assigned - " + endpoint.getLocalName());
					playRingback(participant);
				}
			}

		}
	}
	
	public void playRingback(CallParticipant participant) {		
		String tone = PbxConfiguration.getProperty("pbx.default.ringback.tone");
		log.info("playing following ringbacktone #0", tone);
		IVRHelperManager.instance().getIVRHelper(participant.getSipSession()).playAnnouncementWithDtmf(tone);
	}
	
	private void endRingback(CallParticipant participant) {
		IVRHelperManager.instance().getIVRHelper(participant.getSipSession()).detectDtmf();
	}

	// Extract username from URI
	private String getUser(URI uri) {
		String user = null;
		if(uri.isSipURI()) {
			SipURI suri = (SipURI) uri;
			user = suri.getUser();
		} else {
			TelURL turi = (TelURL) uri;
			user = turi.getPhoneNumber();
		}
		return user;
	}
	
	private String getDialedSubscriber(User fromUser, String dialedDigits)
	{
		return dialedDigits;
	}
*/
	/* (non-Javadoc)
	 * @see javax.servlet.sip.SipServletListener#servletInitialized(javax.servlet.sip.SipServletContextEvent)
	 */
	public void servletInitialized(SipServletContextEvent arg0) {
		// TODO Auto-generated method stub
		IceLog.info(this.getClass(), "SubscriberCallServlet Servlet Initialized");
	}
	
}